import axios, {
  AxiosError,
  AxiosHeaders,
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  CreateAxiosDefaults,
  InternalAxiosRequestConfig,
} from "axios";
import { getCurrentTimeZone } from "./utils";

export enum ContentTypeEnum {
  json = "application/json;charset=UTF-8",
  formData = "multipart/form-data;charset=UTF-8",
}

export type RequestOptions = {
  /**携带token */
  withToken?: boolean;
  /**提示错误 */
  showError?: boolean;
};

export type Result = {
  code: number;
  result: any;
  message: string;
};

export type Options = {
  baseURL: string;
  basePath: string;
};

export default abstract class HttpRequest {
  // 时区
  timeZone: string = getCurrentTimeZone();
  // 语言
  locale: string = getCurrentTimeZone();

  // 私有配置
  private token: string = "";
  private tokenKey: string = "Authorization";
  private reToken: string = "";
  private reTokenKey: string = "refreshToken";
  private axiosInstance: AxiosInstance;
  private responseCache: (() => Promise<any>)[] = [];
  private isRefreshToken: boolean = false;

  constructor(config?: AxiosRequestConfig) {
    // 配置
    this.axiosInstance = axios.create(config);
    // 设置headers附加信息
    this.axiosInstance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        config.headers.Lang = this.locale;
        config.headers.Timezone = this.timeZone;
        config.headers[this.tokenKey] = this.token;
        return config;
      }
    );
  }

  /**
   * 设置token
   * @param value token值
   * @param key token名（默认 Authentication）
   */
  setToken(value: string, key?: string) {
    this.token = value;
    if (key) this.tokenKey = key;
  }

  /**
   * 设置token
   * @param value token值
   * @param key token名（默认 refreshToken）
   */
  setReToken(value: string, key?: string) {
    this.reToken = value;
    if (key) this.reTokenKey = key;
  }

  request<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    let conf: AxiosRequestConfig = config;

    // json格式处理
    if (config.headers?.["Content-Type"] == ContentTypeEnum.json) {
      conf = JSON.parse(JSON.stringify(config));
    }

    // 构造请求
    const responsePromise = () => {
      return new Promise((resolve, reject) => {
        this.axiosInstance
          .request<any, AxiosResponse<Result>>(conf)
          .then(async (res: AxiosResponse<Result>) => {
            try {
              // 默认数据解析
              let { code, result } = res.data;

              // 转换数据
              if (this.transformResponseHook) {
                const transformData = await this.transformResponseHook(res);
                code = transformData.code;
                result = transformData.result;
              }

              console.log("conf", conf);
              console.log("刷新token", this.reToken);

              if (code == 401) {
                if (!this.token) {
                  console.log('没有token，去登录');
                  
                  // 没有token，去登录
                  if (this.logout_401) this.logout_401(res);
                  reject();
                } else if (
                  conf.data?.[this.reTokenKey] == this.reToken ||
                  conf.params?.[this.reTokenKey] == this.reToken
                ) {
                  // 刷新token接口 401
                  reject();
                } else {
                  // 缓存401请求
                  this.responseCache.push(responsePromise);
                  // 刷新token
                  if (
                    this.isRefreshToken == false &&
                    this.refreshToken &&
                    this.reToken
                  ) {
                    this.refreshToken({ [this.reTokenKey]: this.reToken })
                      .then(({ token, reToken }) => {
                        // 设置新token、refreshToken
                        this.setToken(token);
                        this.setReToken(reToken);
                        // 重新发起缓存401请求队列
                        this.responseCache.forEach((fun) => {
                          fun().then(resolve).catch(reject);
                        });
                        // 清空401请求队列
                        this.responseCache = [];
                      })
                      .catch((err) => {
                        // 刷新token失败，跳转登录
                        if (this.logout_401) this.logout_401(res);
                        reject();
                      })
                      .finally(() => {
                        this.isRefreshToken = false;
                      });
                    this.isRefreshToken = true;
                  }
                }
              } else {
                resolve(result);
              }
            } catch (err) {
              const t = err || new Error("request error!");
              if (this.requestCatchHook) {
                this.requestCatchHook(t as Error, options, () => reject(t));
              }
            }
          })
          .catch((e: Error | AxiosError) => {
            console.log("AxiosError");
            if (this.requestCatchHook) {
              this.requestCatchHook(e, options, () => reject(e));
            }
          });
      });
    };

    // 发起请求，返回结果
    return responsePromise() as Promise<T>;
  }

  /**
   * 返回数据处理
   * @param res 返回数据
   */
  abstract transformResponseHook(res: AxiosResponse<Result>): Promise<Result>;

  /**
   * 错误处理
   * @param err 错误信息
   * @param options
   * @param onMessageHide
   */
  abstract requestCatchHook(
    err: Error | AxiosError,
    options?: RequestOptions,
    next?: () => void
  ): void;

  /**
   * 刷新token
   * @param data 刷新token参数
   */
  abstract refreshToken(data: {
    [key: string]: string;
  }): Promise<{ token: string; reToken: string }>;

  /**
   * 登出401（没有token、刷新token接口401）
   * @param data 错误信息
   */
  abstract logout_401(data?: any): Promise<any>;

  get<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    return this.request({ ...config, method: "GET" }, options);
  }

  post<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    return this.request({ ...config, method: "POST" }, options);
  }

  put<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    return this.request({ ...config, method: "PUT" }, options);
  }

  delete<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    return this.request({ ...config, method: "DELETE" }, options);
  }

  upload<T = any>(
    config: AxiosRequestConfig,
    options?: RequestOptions
  ): Promise<T> {
    return this.request(
      {
        method: "POST",
        ...config,
        headers: {
          "Content-type": ContentTypeEnum.formData,
          ...config.headers,
        },
      },
      options
    );
  }
}
